package com.utils.basejava.NIO;


import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Scanner;

/*
 * 一、使用 NIO 完成网络通信的三个核心：
 * 
 * 1. 通道（Channel）：负责连接
 * 		
 * 	   java.nio.channels.Channel 接口：
 * 			|--SelectableChannel
 * 				|--SocketChannel
 * 				|--ServerSocketChannel
 * 				|--DatagramChannel
 * 
 * 				|--Pipe.SinkChannel
 * 				|--Pipe.SourceChannel
 * 
 * 2. 缓冲区（Buffer）：负责数据的存取
 * 
 * 3. 选择器（Selector）：是 SelectableChannel 的多路复用器。用于监控 SelectableChannel 的 IO 状况
 * 
 */
public class TestNonBlockingNIO {

	//客户端
	
	public void client() throws IOException {
		//1、获取通道
		SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
		
		//2、切换非阻塞模式
		sChannel.configureBlocking(false);
		
		//3、分配指定大小的缓冲区
		ByteBuffer bb = ByteBuffer.allocate(1024);
		
		//4、发送数据给服务端
		@SuppressWarnings("resource")
		Scanner scan = new Scanner(System.in);
		
		while(scan.hasNext()) {
			bb.put(scan.next().getBytes());
			bb.flip();
			sChannel.write(bb);
			bb.clear();
		}
		
		//5、关闭通道
		sChannel.close();
	}
	
	//服务端
	
	public void server() throws IOException {
		//1、获取通道
		ServerSocketChannel ssc = ServerSocketChannel.open();
		//2、切换非阻塞模式
		ssc.configureBlocking(false);
		//3、绑定连接
		ssc.bind(new InetSocketAddress(9898));
		
		//1、获取通道
		ServerSocketChannel ssc1 = ServerSocketChannel.open();
		//2、切换非阻塞模式
		ssc1.configureBlocking(false);
		//3、绑定连接
		ssc1.bind(new InetSocketAddress(9899));
		
		//4、获取选择器
		Selector selector = Selector.open();
		
		//	        选择键（SelectorKey）：指注册到Selector上的通道。
		//     已选择键：表示有就绪事件的通道所注册的选择键。
		//5、将通道注册到选择器上，并且指定“监听事件”。并返回一个SelectionKey对象，这个对象代表了注册到该Selector的通道
		SelectionKey register = ssc.register(selector, SelectionKey.OP_ACCEPT);
		register.interestOps();
//		selectionKey.attach(theObject);
		
		SelectionKey register1 = ssc1.register(selector, SelectionKey.OP_ACCEPT);
		register1.attach("attach...");
		
		System.out.println("没有就绪事件时的已选择键数量："+selector.selectedKeys().size());//始终是零 ！！！因为没有被选择的选择键
		
		//6、监控所有注册的channel，当他们中间有需要处理的IO操作时，该方法返回，
		// 并将对应的SelectionKey加入被选择的SelectionKey集合(selectedKeys()方法)中，select()方法返回这些Channel的数量。
		// 轮询式的获取选择器上已经“准备就绪”的事件
		int leng = 0;
		while((leng = selector.select())>0) {//select()方法会一直阻塞到某个注册的通道有事件就绪
			System.out.println("有就绪事件的channel数量："+leng);
			//7、获取当前选择器中已选择的选择键（有已准备就绪的事件的选择键）
			Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
			System.out.println("有就绪事件时的已选择键数量："+selector.selectedKeys().size());

			while(iterator.hasNext()) {
				//8、获取“准备就绪”的选择键
				SelectionKey sk = iterator.next();
//				Object attachedObj = selectionKey.attachment();
				System.out.println("感兴趣的事件类型:"+sk.interestOps());
				//9、判断选择键具体是什么事件准备就绪
				if(sk.isAcceptable()) {
					//10、若“接收就绪”，获取客户端连接
					SocketChannel channel = ssc.accept();
					
					//11、切换非阻塞模式
					channel.configureBlocking(false);
					
					//12、将该通道注册到选择器上
					channel.register(selector, SelectionKey.OP_READ);
					System.out.println("建立连接....");
				}else if(sk.isReadable()) {
					//13、若“读就绪”，获取当前选择器上“读就绪”状态的通道
					SocketChannel channel = (SocketChannel) sk.channel();
					
					//14、读取数据
					ByteBuffer bb = ByteBuffer.allocate(1024);
					
					int len = 0;
					while((len = channel.read(bb))>0) {
						bb.flip();
						System.out.println(new String(bb.array(),0,len));
						bb.clear();
					}
				}else {
					System.out.println("non-all");
				}
				//15、取消选择键 SelectionKey
				System.out.println("------取消选择键-------");
				iterator.remove();
			}
			
		}
		
	}
}
